#pragma once

#include <iostream>
#include <memory>
#include <unistd.h>
#include <sys/poll.h>
#include "Socket.hpp"

using namespace SocketModule;

//poll的优点：
//解决了select有数目上线的问题，同时做到了输入输出参数分离，接口使用更加方便
//poll的缺点：
//和select一样poll返回后，需要轮询pollfd来获取就绪的文件描述符
//文件描述符增多的时候，`效率也会下降，因为连接的大量客户可能只有少量的连接已经就绪了

class PollServer
{
    static const int size = 4096;
    static const int default_fd = -1;
public:
    PollServer(int port)
        : _listensock(std::make_unique<TcpSocket>())
        , _isrunning(false)
    {
        _listensock->BuildListenSocket(port);
        for (int i = 0; i < size; i++)
        {
            _fds[i].fd = default_fd;
            _fds[i].events = 0;
            _fds[i].revents = 0;
        }
        _fds[0].fd = _listensock->Fd();
        _fds[0].events = POLLIN; //events就绪的时候去该revents
    }

    void Dispatcher()
    {
        // 这里即使新连接到来，也是读事件就绪
        for (int i = 0; i < size; i++)
        {
            // 先确定连接一定是就绪了
            if (_fds[i].fd == default_fd)
            {
                // 不合法
                continue;
            }
            if (_fds[i].events & POLLIN) //&用来判断，｜用来赋值
            {
                // 这样fd_array[i]所对应的连接一定是就绪了，接下来确定是那种fd就绪
                if (_fds[i].fd == _listensock->Fd())
                {
                    // 这样就一定是listensockfd的到来
                    Accepter();
                }
                else
                {
                    // 普通的读事件就绪
                    Recver(i);
                }
            }
        }
    }

    void Recver(int pos)
    {
        char buffer[1024];
        while (true)
        {
            ssize_t n = recv(_fds[pos].fd, buffer, sizeof(buffer) - 1, 0);
            if (n > 0)
            {
                buffer[n] = 0;
                std::cout << buffer << std::endl;
            }
            else if (n == 0)
            {
                std::cout << "client exit" << std::endl;
                // 我们希望后续不再关心这个文件描述符
                close(_fds[pos].fd);
                _fds[pos].fd = default_fd;
                break;
            }
            else
            {
                std::cout << "error occurs" << std::endl;
                close(_fds[pos].fd);
                break;
            }
        }
    }

    void Accepter()
    {
        // 获取连接
        InitAddr client;
        int fd = _listensock->Accept(client); // 从底层获取链接进行处理，只要Accepter能够被调用，就一定准备就绪了
        if (fd >= 0)
        {
            // 怎样将我们的新的文件描述符交给select管理呢？
            std::cout << "got a new connetion:" << fd
                      << " the client is:" << client.StringAddr() << std::endl;
            int pos = 0;
            for (; pos < size; pos++)
            {
                if (_fds[pos].fd == default_fd)
                {
                    break; //找到可以使用的文件描述符数组位置，将新加入的链接加入进去
                }
            }
            if (pos == size)
            {
                std::cout << "the select server is full" << std::endl;
                close(fd);
            }
            else
            {
                _fds[pos].fd = fd;
                _fds[pos].events = POLLIN;
            }
        }
    }

    void Start()
    {
        int timeout = -1;
        _isrunning = true;
        while (_isrunning)
        {
            int n = poll(_fds, size, timeout);
            switch (n)
            {
            case -1:
                std::cout << "error occurs" << std::endl;
                break;
            case 0:
                std::cout << "time out" << std::endl;
                break;
            default:
                std::cout << "a mission has arrived" << std::endl;
                // 没有调用accept，没有将连接从内核获取到应用层，没有对其处理。
                // 设计一个对新连接进行处理的函数
                Dispatcher();
                break;
            }
        }
        _isrunning = false;
    }

    void Stop()
    {
        _isrunning = false;
    }

    ~PollServer()
    {
    }

private:
    std::unique_ptr<Socket> _listensock;
    bool _isrunning = false;
    struct pollfd _fds[size];·
};